﻿using RockBus.Logging.Interfaces;
using System;
using System.IO;
using System.Reflection;

namespace RockBus.Transport.Db
{
    public class ReadableDechunkingStream : Stream
    {
        public ReadableDechunkingStream(ILogger logger, long length, int chunkCount, Func<int, byte[]> retrieveChunkFunc)
        {
            this.Logger = logger;
            this._length = length;
            this.ChunkCount = chunkCount;
            this.RetrieveChunkFunc = retrieveChunkFunc;
        }

        public ILogger Logger { get; set; }

        private int _chunkSize;

        public int ChunkSize
        {
            get { return this._chunkSize; }
        }

        private int ChunkIndex { get; set; }

        private byte[] ChunkBuffer { get; set; }

        public bool HasChunk { get { return (null != this.ChunkBuffer); } }

        private int ChunkOffset { get; set; }

        private int ChunkRemainder { get { return this.ChunkSize - this.ChunkOffset; } }

        public int ChunkCount { get; set; }

        private Func<int, byte[]> RetrieveChunkFunc { get; set; }

        public override bool CanRead
        {
            get { return true; }
        }

        public override bool CanSeek
        {
            get { return false; }
        }

        public override bool CanWrite
        {
            get { return false; }
        }

        public override void Flush()
        {
            // Intentionally left blank
        }

        private readonly long _length;

        public override long Length
        {
            get { return _length; }
        }

        private long _position = 0;

        public override long Position
        {
            get { return this._position; }
            set { throw new NotImplementedException(); }
        }

        public override int Read(byte[] buffer, int offset, int count)
        {
            this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "RDS: stage 0: {0}, {1}, {2}, {3}, {4}", offset, count, this.ChunkOffset, this.ChunkIndex, this.Position);

            if (this.Position >= this.Length)
            {
                return 0;
            }

            if (!this.HasChunk)
            {
                this.RetrieveNextChunk();
            }

            int bytesRead = 0;
            while (this.HasChunk && (count > 0) && (count >= this.ChunkRemainder))
            {
                this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "RDS: stage 1: {0}, {1}, {2}, {3}, {4}", offset, count, this.ChunkOffset, this.ChunkIndex, this.Position);

                Buffer.BlockCopy(this.ChunkBuffer, ChunkOffset, buffer, offset, this.ChunkRemainder);
                this._position += this.ChunkRemainder;

                offset += this.ChunkRemainder;
                count -= this.ChunkRemainder;
                bytesRead += this.ChunkRemainder;

                RetrieveNextChunk();
            }

            if (this.HasChunk && (count > 0))
            {
                this.Logger.LogVerbose(MethodBase.GetCurrentMethod(), "RDS: stage 2: {0}, {1}, {2}, {3}, {4}", offset, count, this.ChunkOffset, this.ChunkIndex, this.Position);

                Buffer.BlockCopy(this.ChunkBuffer, this.ChunkOffset, buffer, offset, count);
                this._position += count;

                this.ChunkOffset += count;
                bytesRead += count;
                count = 0;
            }

            return bytesRead;
        }

        public override long Seek(long offset, SeekOrigin origin)
        {
            throw new NotImplementedException();
        }

        public override void SetLength(long value)
        {
            throw new NotImplementedException();
        }

        public override void Write(byte[] buffer, int offset, int count)
        {
            throw new NotImplementedException();
        }

        private bool RetrieveNextChunk()
        {
            if (this.ChunkIndex < this.ChunkCount)
            {
                this.ChunkBuffer = this.RetrieveChunkFunc(this.ChunkIndex);
                this._chunkSize = this.ChunkBuffer.Length;
            }
            else
            {
                this.ChunkBuffer = null;
                this._chunkSize = 0;
            }
            this.ChunkIndex++;
            this.ChunkOffset = 0;

            return this.HasChunk;
        }
    }
}